﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

/*
*/
namespace DbView
{
	/*
		Commands drive data table creation
	*/
	public class Commands
	{
		// known commands
		public enum Cmnd
		{
			Nothing,
			Tables,
			TableSource,
			TableFieldSearch,
			TableData,
			Fields,
			ShowSource,
			SearchSource,
			Views,
			ViewSource,
			ViewSourceSearch,
			Procedures,
			ProcedureSource,
			ProcedureSourceSearch,
			Functions,
			FunctionSource,
			FunctionSourceSearch,
			Triggers,
			TriggerSource,
			TriggerSourceSearch,
			Computed,
			ComputedSourceSearch,
			AllSourceSearch,
			Indexes,
			ForeignKeys,
			Dependencies,
			Permissions,
			Aggregate,
			RowCount,
			MinMax,
			Average,
			LongShort,
			Profile,
			ShowProfileData,
			History,
			EditView,
			Templates,
			ShowCommand,
			Adhoc,
			Droptable,
			Cleartable,
			AddColumn,
			ChangeColumn,
			RemoveColumn,
			Import,
			Export,
			Excel,
			Extension // future use. This flags the item as an Extension.
		};

		public static Command BuildCommand(Commands.Cmnd command)
		{
			Command c = new Command(command);
			// if (command == cmd.TriggerSource)
			return c;
		}

		/* not needed I hope
		Dictionary<cmd, String> m_Translator;
		
		public Commands()
		{
			this.m_Translator = new Dictionary<cmd,string>();
		}
		
		public void MapStandardCommand(cmd command, String commandTag)
		{
			this.m_Translator.Add(command, commandTag);
		}
		
		public String TranslateStandardCommand(cmd command)
		{
			String commandTag = ""; // indicates not supported
			if (this.m_Translator.ContainsKey(command))
				commandTag = m_Translator[cmd];
			return commandTag;
		}
		*/
		
		public static void ResetHelp(CommandHelpRepository helpRepository)
		{
			helpRepository.SetHelp(new Command(Cmnd.ShowCommand), "Show the sql used to produce the results grid");
			helpRepository.SetHelp(new Command(Cmnd.Tables), "List the tables in the database");
			helpRepository.SetHelp(new Command(Cmnd.TableSource), "Generate a script that will create this table");
			helpRepository.SetHelp(new Command(Cmnd.TableFieldSearch), "cmd.TableFieldSearch TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.TableData), "Display data rows from the table");
			helpRepository.SetHelp(new Command(Cmnd.Fields), "Display the fields/columns for the table");
			helpRepository.SetHelp(new Command(Cmnd.ShowSource), "Show the SQL source of the view object");
			helpRepository.SetHelp(new Command(Cmnd.SearchSource), "Search views or stored procedures for this object name");
			helpRepository.SetHelp(new Command(Cmnd.Views), "Views TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.ViewSource), "Show the Sql source of the view");
			helpRepository.SetHelp(new Command(Cmnd.ViewSourceSearch), "ViewSourceSearch TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Procedures), "Procedures TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.ProcedureSource), "Show the sql source of the stored procedure");
			helpRepository.SetHelp(new Command(Cmnd.ProcedureSourceSearch), "ProcedureSourceSearchTODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Functions), "Functions TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.FunctionSource), "Show the Sql source of the function");
			helpRepository.SetHelp(new Command(Cmnd.FunctionSourceSearch), "FunctionSourceSearch TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Triggers), "Triggers TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.TriggerSource), "Show the Sql source of the trigger");
			helpRepository.SetHelp(new Command(Cmnd.TriggerSourceSearch), "TriggerSourceSearch TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Computed), "Computed TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.ComputedSourceSearch), "ComputedSourceSearch TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Indexes), "Indexes TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Dependencies), "Show objects that depend on this object or that this object depends on");
			helpRepository.SetHelp(new Command(Cmnd.Aggregate), "Aggregate TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.RowCount), "RowCount TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.MinMax), "TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Average), "TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Profile), "Show the row data using a row for each column");
			helpRepository.SetHelp(new Command(Cmnd.ShowProfileData), "TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.History), "TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.EditView), "Change the parameters of this data display");
			helpRepository.SetHelp(new Command(Cmnd.Templates), "Templates TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Adhoc), "TODO: add proper help");
			helpRepository.SetHelp(new Command(Cmnd.Droptable), "Delete the table");
			helpRepository.SetHelp(new Command(Cmnd.Cleartable), "Delete all of the rows in the table");
			helpRepository.SetHelp(new Command(Cmnd.AddColumn), "Add a new column to the table");
			helpRepository.SetHelp(new Command(Cmnd.ChangeColumn), "Change the column definition");
			helpRepository.SetHelp(new Command(Cmnd.RemoveColumn), "Remove the column from the table");
			helpRepository.SetHelp(new Command(Cmnd.Import), "Import data from a CSV file into the table (nb: table must be empty)");
			helpRepository.SetHelp(new Command(Cmnd.Export), "Export the contents of the table to a CSV file");
			helpRepository.SetHelp(new Command(Cmnd.Excel), "Save the contents of the currently showing grid to an Excel file");
		}
	}

	public class Command
	{
		public enum CommandAction {
			poptable,        // populates a table for display in the grid
			text,			 // generates text (eg. a script) for display in the source window
			sysaction,		 // application special action
			driveraction	 // driver-specific special action
		}

/*
		// deprecated
		// constructor for custom commands
		public Command(Commands.cmd command, String commandTag, bool popTable)
		{
			Cmd = command;
			Name = commandTag;
			PopulatesTable = popTable; 
		}
*/
		// constructor for standard commands
		public Command(Commands.Cmnd command)
		{
			this.Cmd = command;
			this.Name = command.ToString();
			this.Action = CommandAction.poptable;
			this.ConfigureKnownCommands();
		}

		// constructor for custom commands
		public Command(Commands.Cmnd command, String commandTag, CommandAction act)
		{
			this.Cmd = command;
			this.Name = commandTag;
			this.Action = act;
		}

		public static Command MakeCommand(String commandString)
		{
			Commands.Cmnd cmd = InternalTranslateCommand(commandString);
			return new Command(cmd);
		}

		public Command(String xml)
		{
			XmlDocument doc = new XmlDocument();
			try
			{
				doc.LoadXml(xml);
				String command = this.AttributeValue(doc.DocumentElement, "name");
				String customFlag = this.AttributeValue(doc.DocumentElement, "custom", "N");  // default not custom
				String actionString = this.AttributeValue(doc.DocumentElement, "action", "");
				// Later on get the parameters.
				XmlNodeList paramNodes = doc.DocumentElement.SelectNodes("param");
				if (paramNodes != null)
				{
					foreach(XmlNode paramNode in paramNodes)
					{
						String param =  this.AttributeValue(paramNode, "name");
						String value =  this.AttributeValue(paramNode, "value");
						if (param.Length > 0) this.AddParam(param, value);
					}
				}
				// build the insides
				// custom if flagged or unrecognised
				this.Cmd = (customFlag == "Y") ? Commands.Cmnd.Extension : InternalTranslateCommand(command);
				// adopy the xml name if custom
				if (this.Cmd == Commands.Cmnd.Extension)
					this.Name = command;
				// figure out the action
				this.Action = this.InternalTranslateAction(actionString);
			}
			catch (XmlException xexc)
			{
				String message = String.Format("Error parsing command memo {0}\n{1}", xml, xexc.Message);
				throw new ApplicationException(message);
			}
		}

		public Commands.Cmnd Cmd { get; set; }
		public String Name { get; set; }
		public CommandAction Action {get; set; }
		
		public bool IsCustomCommand { get { return this.Cmd == Commands.Cmnd.Extension; } }
		public void AddParam(String param, String value)
		{
			if (this.parameters == null) this.parameters = new Dictionary<string, string>();
			// allow add/update
			if (this.parameters.ContainsKey(param))
				this.parameters[param] = value; // update 
			else
				this.parameters.Add(param, value);
		}

		public String this[String key]
		{
			get
			{
				if (this.parameters == null || !this.parameters.ContainsKey(key)) return "";
				return this.parameters[key];
			}
		}

		// return quick and dirty list in form key = value
		public List<String> GetParamInfo()
		{
			List<String> paramInfo = new List<string>();
			if (this.parameters != null)
			{
				foreach (String key in this.parameters.Keys)
				{
					paramInfo.Add(String.Format("{0} = {1}", key, this.parameters[key]));
				}
				paramInfo.Sort();
			}
			return paramInfo;
		}

		// encapsulate a standard way to name columns
		public static String TableParameterName(int col)
		{
			return String.Format("COLUMN{0}", col + 1);  // ie. datarow[0] -> COLUMN1
		}

		public override string ToString()
		{
			if (this.Cmd == Commands.Cmnd.Extension)
				return String.Format("{0}:{1}", this.Cmd, this.Name); // plus tag if extention
			return this.Cmd.ToString();
		}

		public string ToXml()
		{
			// new regime name resolves to .cmd if custom='N'
			// params added. default none.
			// pop=Y for table creating. default = Y
			String customFlag = (this.IsCustomCommand) ? "Y" : "N";
			String command = (this.IsCustomCommand) ? this.Name : this.Cmd.ToString();
			XmlDocument doc = new XmlDocument();
			doc.AppendChild(doc.CreateElement("command"));
			this.AddAttribute(doc.DocumentElement, "name", command);
			this.AddAttribute(doc.DocumentElement, "custom", customFlag);
			this.AddAttribute(doc.DocumentElement, "action", this.Action.ToString());
			//
			if (this.parameters != null)
			{
				foreach (String key in this.parameters.Keys)
				{
					XmlNode paramNode = doc.CreateElement("param");
					this.AddAttribute(paramNode, "name", key);
					this.AddAttribute(paramNode, "value", this.parameters[key]);
					doc.DocumentElement.AppendChild(paramNode);
				}
			}
			return doc.OuterXml;
		}

		public bool IsSameAs(Command theOther, bool testParametersAsWell)
		{
			// extension commands test for equality by name
			if (this.Cmd == Commands.Cmnd.Extension && theOther.Cmd == Commands.Cmnd.Extension)
			{
				if (this.Name != theOther.Name) return false; // nope no match
				// names match and no param check then shoe-in match else test the parameters
				return (!testParametersAsWell) ? true : this.HasSameParamsAs(theOther);
			}
			if (this.Cmd != theOther.Cmd) return false; // nope no match
			// same reasoning as above.
			return (!testParametersAsWell) ? true : this.HasSameParamsAs(theOther);
		}
		
		bool HasSameParamsAs(Command theOther)
		{
			if (this.parameters == null && theOther.parameters == null) return true; // twp null sets = match
			if (this.parameters == null || theOther.parameters == null) return false; // one or other (not both) is null  = no match
			if (this.parameters.Count != theOther.parameters.Count) return false;   // count does not match = no match
			// at this point each command has the same positive number of params 
			// so for each one look up the other and compare values.
			foreach (String key in this.parameters.Keys)
			{
				String v1 = this.parameters[key];
				if (theOther[key] != v1) return false;  // value does not match (or key not found (unlikely))
			}
			return true;
		}

		static bool TestForEquality(object obj1, object obj2)
		{
			if (obj1 == null)
			{
				return false;
			}

			return obj1.Equals(obj2);
		}

		// extended construction for known commands. Typically overwrit the default
		// Pop table option
		void ConfigureKnownCommands()
		{
			if (this.Cmd == Commands.Cmnd.TableSource ||
				this.Cmd == Commands.Cmnd.ViewSource ||
				this.Cmd == Commands.Cmnd.ProcedureSource ||
				this.Cmd == Commands.Cmnd.TriggerSource ||
				this.Cmd == Commands.Cmnd.FunctionSource ||
				this.Cmd == Commands.Cmnd.ShowSource)
				this.Action = CommandAction.text; // overwrites the populates table

			if (this.Cmd == Commands.Cmnd.ShowCommand ||
				this.Cmd == Commands.Cmnd.EditView ||
				this.Cmd == Commands.Cmnd.SearchSource ||
				this.Cmd == Commands.Cmnd.Import ||
				this.Cmd == Commands.Cmnd.Export ||
				this.Cmd == Commands.Cmnd.Excel)
				this.Action = CommandAction.sysaction;

			if (this.Cmd == Commands.Cmnd.Droptable ||
				this.Cmd == Commands.Cmnd.Cleartable ||
				this.Cmd == Commands.Cmnd.AddColumn ||
				this.Cmd == Commands.Cmnd.ChangeColumn ||
				this.Cmd == Commands.Cmnd.RemoveColumn
				)
				this.Action = CommandAction.driveraction;
		}

		static Commands.Cmnd InternalTranslateCommand(String commandString)
		{
			if (commandString.Length == 0) return Commands.Cmnd.Nothing;
			foreach (Commands.Cmnd c in Enum.GetValues(typeof(Commands.Cmnd)))
			{
				if (c.ToString() == commandString) return c;
			}
			return Commands.Cmnd.Extension;
		}

		CommandAction InternalTranslateAction(String actionString)
		{
			if (actionString.Length == 0) return CommandAction.poptable; // default fall bacl
			foreach (CommandAction act in Enum.GetValues(typeof(CommandAction)))
			{
				if (act.ToString() == actionString) return act;
			}
			return CommandAction.poptable;
		}

		public static Command Translate(String commandString)
		{
			foreach (Commands.Cmnd c in Enum.GetValues(typeof(Commands.Cmnd)))
			{
				if (c.ToString() == commandString) return new Command(c);
			}
			//
			// Hack. The string may be Xml
			//
			if (commandString.IndexOf("<") != -1 && commandString.IndexOf(">") != -1)
				return new Command(commandString);
			
			// see above
			String[] split = commandString.Split(':');
			if (split.GetLength(0) == 2)
			{
				// should be 
				Command command = new Command(Commands.Cmnd.Extension, split[1], CommandAction.poptable);
				return command;
			}
			return new Command(Commands.Cmnd.Nothing); // not known to the framework
		}

		Dictionary<String, String> parameters; // lazy instantiation

		//////////////////////////////////////////////////////////////////////
		// Xml helpers
		public void AddAttribute(XmlNode node, String attName, String attVal)
		{
			XmlAttribute att = node.Attributes[attName];
			if (att == null)
			{
				att = node.OwnerDocument.CreateAttribute(attName);
				node.Attributes.Append(att);
			}
			att.Value = attVal;
		}

		public String AttributeValue(XmlNode node, String attName, String defaultVal)
		{
			XmlAttribute att = node.Attributes[attName];
			if (att == null) return defaultVal;
			return att.Value;
		}

		public String AttributeValue(XmlNode node, String attName)
		{
			return this.AttributeValue(node, attName, "");
		}

		public String ElementText(XmlNode node, String elementName)
		{
			XmlNode tgtNode = node.SelectSingleNode(elementName);
			if (tgtNode == null) return "";
			return tgtNode.InnerText;
		}
	}

	/*
		Experimental. Create a repository for right-click menu help.
		This is intended to be shared between the main routine and the drivers
		The main routine should set the help for the known commands and
		the drivers set the help for their own.
	*/
	public class CommandHelpRepository
	{
		public static CommandHelpRepository Instance
		{
			get
			{
				if (theRepository == null)
					theRepository = new CommandHelpRepository();
				return theRepository;
			}
		}
		
		// hidden singleton constructor
		CommandHelpRepository()
		{
			// different key strategy depending on whether or not the command is known 
			// or a driver extension command.
			this.commandHelpStore = new Dictionary<Command, String>();
			this.extensionCommandHelpStore = new Dictionary<String, String>();
		}
		
		public void SetHelp(Command command, String help)
		{
			if (true)
			//if (command.Cmd == Commands.cmd.Extension)
			{
				if (!this.extensionCommandHelpStore.ContainsKey(command.Name))
					this.extensionCommandHelpStore.Add(command.Name, help);
				else
					this.extensionCommandHelpStore[command.Name] = help;
			}
			//else
			//{
			//    if (!CommandHelpStore.ContainsKey(command))
			//        CommandHelpStore.Add(command, help);
			//    else
			//        CommandHelpStore[command] = help;
			//}
		}

		public String GetHelp(Command command)
		{
			if (true)
//			if (command.Cmd == Commands.cmd.Extension)
			{
				if (!this.extensionCommandHelpStore.ContainsKey(command.Name)) return "";
				return this.extensionCommandHelpStore[command.Name];
			}
			//if (!CommandHelpStore.ContainsKey(command)) return "";
			//return CommandHelpStore[command];
		}

		Dictionary<Command, String> commandHelpStore;
		Dictionary<String, String> extensionCommandHelpStore;
		
		static CommandHelpRepository theRepository = null;
	}
}
